package bert;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;

import bert.collection.extensions.CX;
import bert.collection.extensions.Predicate;

public class TrackSegment {

    public static class Link {

        public Link(TrackSegment target, TrackSegment source,
                Side adjacentSideTarget, Side adjacentSideSource) {
            Target = target;
            Source = source;
            AdjacentSideTarget = adjacentSideTarget;
            AdjacentSideSource = adjacentSideSource;
        }
        public final TrackSegment Target;
        public final TrackSegment Source;
        public final Side AdjacentSideTarget;
        public final Side AdjacentSideSource;
    }
    
    public final Polygon Poly;
    private final float[] vertexWeights;
    
    private final ArrayList<Link> neighbours = new ArrayList<Link>();
    private final ArrayList<TrackSegment> in = new ArrayList<TrackSegment>();
    private final ArrayList<TrackSegment> out = new ArrayList<TrackSegment>();

    public TrackSegment(Polygon p) {
        assert (p != null);

        this.Poly = p;
        
        this.vertexWeights = new float[p.GetVertexCount()];
        for (int i = 0; i < this.vertexWeights.length; ++i)
        	this.vertexWeights[i] = -1;
    }

    public Collection<TrackSegment> GetPredecessors() {
        return Collections.unmodifiableCollection(this.in);
    }

    public Collection<TrackSegment> GetSuccessors() {
        return Collections.unmodifiableCollection(this.out);
    }

    private boolean EdgeDirectionDetermined() {
        return this.in.size() + this.out.size() == this.neighbours.size();
    }

    public static TrackModel CreateTrack(RawTrackGeometry trackGeometry) {
        final Collection<Polygon> ps = PolyFitter.apply(trackGeometry.Polygons, 10);

        ArrayList<TrackSegment> segments = new ArrayList<TrackSegment>(ps.size());

        for (Polygon p : ps) {
            segments.add(new TrackSegment(p));
        }

        LinkSegments(segments);

        TrackSegment firstSegment = null;
        TrackSegment lastSegment = null;

        for (TrackSegment seg : segments) {
            if (seg.Poly.Id == trackGeometry.First.Id) {
                assert firstSegment == null;
                firstSegment = seg;
            } else if (seg.Poly.Id == trackGeometry.Last.Id) {
                assert lastSegment == null;
                lastSegment = seg;
            }
        }

        assert firstSegment != null;
        assert lastSegment != null;

        DetermineEdgeDirections(segments, firstSegment, lastSegment);
        DetermineEdgeWeights(segments, firstSegment, lastSegment);

        return new TrackModel(segments, firstSegment, lastSegment);
    }

    private static void DetermineEdgeDirections(Collection<TrackSegment> segments, TrackSegment first, TrackSegment last) {
        final ArrayList<TrackSegment> todo = new ArrayList<TrackSegment>(segments);

        first.in.add(last);
        last.out.add(first);

        while (todo.size() > 0) {
            int preSize = todo.size();

            TrackSegment unambiguousSegment = FindUnambiguousSegment(todo);

            if (unambiguousSegment != null) {

                TrackSegment successor = null;

                for (TrackSegment.Link neighbour : unambiguousSegment.neighbours) {
                    if (!unambiguousSegment.in.contains(neighbour.Target)) {
                        successor = neighbour.Target;
                        break;
                    }
                }

                assert successor != null;

                unambiguousSegment.out.add(successor);
                successor.in.add(unambiguousSegment);
                assert unambiguousSegment.EdgeDirectionDetermined();

                todo.remove(unambiguousSegment);
            } else {
                for (TrackSegment seg : todo) {
                    if (seg.in.size() + seg.out.size() < seg.neighbours.size()
                            && seg.in.size() > 0
                            && seg.out.isEmpty()) {

                        for (TrackSegment.Link neighbours : seg.neighbours) {
                            if (!seg.in.contains(neighbours.Target)) {
                                seg.out.add(neighbours.Target);
                                neighbours.Target.in.add(seg);
                            }
                        }

                        todo.remove(seg);

                        break;
                    }
                }
            }

            if (todo.size() == preSize) {
                throw new RuntimeException();
            }
        }
    }
    
    private static void DetermineEdgeWeights(ArrayList<TrackSegment> segments, 
    		TrackSegment first, TrackSegment last)
    {
    	
    }

    private HashSet<Integer> FindIncomingVertices()
    {
    	HashSet<Integer> incomingVertices = new HashSet<Integer>();
    	Vec2[] vertices = this.Poly.GetVertices();
    	
        Collection<Link> incomingsLinks = CX.Filter(this.neighbours, new Predicate<Link>() {
        	@Override public boolean Matches(Link link) {
				assert link.Source == TrackSegment.this;
				return TrackSegment.this.in.contains(link.Target);
			}
		});
    	
        for (final Link incoming : incomingsLinks)
		{
			int indexA = CX.findFirstIndex(vertices, new Predicate<Vec2>() {
				@Override public boolean Matches(Vec2 obj) {
					return obj.isEquivalentTo(incoming.AdjacentSideSource.PointA);
				}
			});
			
			int indexB = CX.findFirstIndex(vertices, new Predicate<Vec2>() {
				@Override public boolean Matches(Vec2 obj) {
					return obj.isEquivalentTo(incoming.AdjacentSideSource.PointB);
				}
			});
			
			incomingVertices.add(indexA);
			incomingVertices.add(indexB);
		}
        
        return incomingVertices;
    }
    
    /*private static ArrayList<Link> FindLongestPath(TrackSegment from, TrackSegment to)
    {
    	
    }*/
    
    /*private static void FindAllPaths(TrackSegment from, TrackSegment to, 
    		ArrayList<Link> currentPath, ArrayList<ArrayList<Link>> paths)
    {
    	
    	
    }*/
    
    private static TrackSegment FindUnambiguousSegment(ArrayList<TrackSegment> segments) {
        for (TrackSegment seg : segments) {
            if (seg.neighbours.size() == 2 && seg.in.size() == 1) {
                return seg;
            }
        }

        return null;
    }

    private static void LinkSegments(Collection<TrackSegment> segments) {
        for (TrackSegment seg1 : segments) {
            for (TrackSegment seg2 : segments) {
                if (seg1 == seg2) {
                    continue;
                }

                if (seg1.Poly.isAdjacentTo(seg2.Poly)) {
//                    seg1.neighbours.add(
//                            new Link(
//                            seg2,
//                            seg1,
//                            seg2.Poly.adjacentSideWith(seg1.Poly),
//                            seg1.Poly.adjacentSideWith(seg2.Poly)));
//
//                    seg2.neighbours.add(
//                            new Link(
//                            seg1,
//                            seg2,
//                            seg1.Poly.adjacentSideWith(seg2.Poly),
//                            seg2.Poly.adjacentSideWith(seg1.Poly)));
                    LinkNeighbours(seg1, seg2);
                }
            }
        }

        if (!LinkageConsistent(segments)) {
            throw new RuntimeException();
        }
    }

    public boolean HasNeighbour(TrackSegment other) {
        for (TrackSegment.Link link : this.neighbours) {
            if (link.Target == other) {
                return true;
            }
        }

        return false;
    }

    private static void LinkNeighbours(TrackSegment s1, TrackSegment s2) {

        if (s1.HasNeighbour(s2)) {
            if (!s2.HasNeighbour(s1)) {
                throw new RuntimeException("Inconsitent linkage");
            }
        } else {

            s1.neighbours.add(
                    new Link(
                    s2,
                    s1,
                    s2.Poly.adjacentSideWith(s1.Poly),
                    s1.Poly.adjacentSideWith(s2.Poly)));

            s2.neighbours.add(
                    new Link(
                    s1,
                    s2,
                    s1.Poly.adjacentSideWith(s2.Poly),
                    s2.Poly.adjacentSideWith(s1.Poly)));
        }
    }

    private boolean linkageConsistent() {
        for (Link neighbourLink : this.neighbours) {
            if (neighbourLink.Source != this) {
                return false;
            }

//			if(!this.Poly.sides().contains(neighbourLink.AdjacentSideSource))
//				return false;

            //neighbourLink.Target.neighbours

        }

        return true;
    }

    private static boolean LinkageConsistent(Collection<TrackSegment> segments) {
        for (TrackSegment seg : segments) {
            if (!seg.linkageConsistent()) {
                return false;
            }
        }

        return true;
    }
}